home *** CD-ROM | disk | FTP | other *** search
/ Linux Cubed Series 7: Sunsite / Linux Cubed Series 7 - Sunsite Vol 1.iso / search / lsmtool-.6 / lsmtool- / lsmtool-0.6 / tool.c < prev    next >
C/C++ Source or Header  |  1995-01-01  |  16KB  |  813 lines

  1. /*
  2.  * tool.c -- user interface for an LSM database
  3.  *
  4.  * Lars Wirzenius
  5.  * "@(#)lsmtool:tool.c,v 1.14 1995/01/01 16:04:17 wirzeniu Exp"
  6.  */
  7.  
  8.  
  9. #include <assert.h>
  10. #include <ctype.h>
  11. #include <stdlib.h>
  12. #include <string.h>
  13. #include <signal.h>
  14. #include <setjmp.h>
  15. #include <publib.h>
  16.  
  17. #include "lsm.h"
  18. #include "term.h"
  19.  
  20.  
  21. /*
  22.  * Help screen.
  23.  */
  24. static void help(void) {
  25.     static const char *txt[] = {
  26. "LSMTOOL by Lars Wirzenius",
  27. "",
  28. "h, H    Help",
  29. "q, Q    Quit",
  30. "n, N    Next entry",
  31. "p, P    Previous entry",
  32. "/       Search forward",
  33. "?, \\    Search backward",
  34. "spc     Next page",
  35. "-       Previous page",
  36. "return,",
  37. "+, i, I switch between list and entry modes",
  38. "g       First entry",
  39. "G       Last entry (notice differece in case!)",
  40. "w, W    Write entry to file",
  41. "d, D    Delete entry",
  42. "s, S    Save database into file (ask for name)",
  43. "$       Sort the database",
  44. "u, U    Find next dUplicate (same title)",
  45. "",
  46. "---Press space to continue---",
  47.     };
  48.     int i;
  49.  
  50.     clear();
  51.     for (i = 0; i < sizeof(txt)/sizeof(*txt); ++i) {
  52.         move(i, 0);
  53.         printf("%.*s", termwidth()-2, txt[i]);
  54.     }
  55.     while (getkey() != ' ')
  56.         continue;
  57. }
  58.  
  59.  
  60.  
  61.  
  62.  
  63. /* Sort the database.  */
  64. static int field = -1;
  65. static int entry_cmp(const void *e1, const void *e2) {
  66.     const struct lsm_entry *ee1 = e1;
  67.     const struct lsm_entry *ee2 = e2;
  68.     assert(field >= 0);
  69.     return strcmp(ee1->fields[field], ee2->fields[field]);
  70. }
  71. static void sort_database(struct lsm_database *db) {
  72.     int f, i;
  73.     struct lsm_entry *e;
  74.     char dummy[] = "";
  75.  
  76.     f = lsm_field_to_index("Title");
  77.     field = lsm_field_to_index(" temp ");
  78.     for (i = 0; i < db->nentries; ++i) {
  79.         e = &db->entries[i];
  80.         if (e->fields[f] == NULL)
  81.             e->fields[field] = dummy;
  82.         else
  83.             e->fields[field] = strtrim(xstrdup(e->fields[f]));
  84.     }
  85.  
  86.     qsort(db->entries, db->nentries, sizeof(*db->entries), entry_cmp);
  87.  
  88.     for (i = 0; i < db->nentries; ++i) {
  89.         e = &db->entries[i];
  90.         if (e->fields[field] != dummy)
  91.             free(e->fields[field]);
  92.         e->fields[field] = NULL;
  93.     }
  94. }
  95.  
  96.  
  97.  
  98.  
  99. /*
  100.  * Return the next duplicate, starting with entry 'i'.  Return the index
  101.  * of the entry that was found, or 'i' if not found.
  102.  */
  103. static same_title(struct lsm_entry *e1, struct lsm_entry *e2) {
  104.     static int title = -1;
  105.     char s1[10240], s2[sizeof(s1)];
  106.  
  107.     if (title == -1)
  108.         title = lsm_field_to_index("Title");
  109.     if (e1->fields[title] == e2->fields[title])
  110.         return 1;
  111.     if (e1->fields[title] == NULL || e2->fields[title] == NULL)
  112.         return 0;
  113.     strmaxcpy(s1, e1->fields[title], sizeof(s1));
  114.     strmaxcpy(s2, e2->fields[title], sizeof(s2));
  115.     strtrim(s1);
  116.     strtrim(s2);
  117.     return strcasecmp(s1, s2) == 0;
  118. }
  119.  
  120. static int next_duplicate(struct lsm_database *db, int i) {
  121.     struct lsm_entry *e1, *e2;
  122.     int start, title;
  123.  
  124.     if (i+1 >= db->nentries)
  125.         return 0;
  126.  
  127.     title = lsm_field_to_index("Title");
  128.     start = i;
  129.     e1 = &db->entries[i];
  130.     for (; i+1 < db->nentries; ++i) {
  131.         e2 = &db->entries[i+1];
  132.         if (same_title(e1, e2))
  133.             return i+1;
  134.         e1 = e2;
  135.     }
  136.     return start;
  137. }
  138.  
  139.  
  140.  
  141.  
  142.  
  143. /*
  144.  * Starting with entry `i', search forward (dir=1) or backward (dir=-1)
  145.  * for the next entry whose any field contains the substring "pat".  Return
  146.  * index of entry found, or the starting index if not found.  The `i' entry
  147.  * is not included in the search.
  148.  */
  149. static int search(struct lsm_database *db, int i, int dir, const char *pat) {
  150.     int j, k;
  151.     struct lsm_entry *e;
  152.  
  153.     for (j = i+dir; j >= 0 && j < db->nentries; j += dir) {
  154.         e = &db->entries[j];
  155.         for (k = 0; k < MAX_FIELDS; ++k)
  156.             if (e->fields[k] != NULL)
  157.                 if (strstr(e->fields[k], pat) != NULL)
  158.                     return j;
  159.     }
  160.     return i;
  161. }
  162.  
  163.  
  164.  
  165. /*
  166.  * Draw an empty box with the upper left corner at (row, col) and being
  167.  * h lines high and w chars wide (including the border).
  168.  */
  169. static void empty_box(int row, int col, int w, int h) {
  170.     int i;
  171.  
  172.     move(row, col);
  173.     printf("+");
  174.     for (i = 0; i < w-2; ++i)
  175.         printf("-");
  176.     printf("+");
  177.  
  178.     for (i = 1; i < h-1; ++i) {
  179.         move(row+i, col);
  180.         printf("|%*s|", w-2, "");
  181.     }
  182.  
  183.     move(row+h-1, col);
  184.     printf("+");
  185.     for (i = 0; i < w-2; ++i)
  186.         printf("-");
  187.     printf("+");
  188. }
  189.  
  190.  
  191.  
  192. /*
  193.  * Display a message to the user, and wait for a keypress.
  194.  */
  195. static void notify(const char *msg) {
  196.     int h, w, borderx, bordery;
  197.     const char anykey[] = "Press any key to continue";
  198.  
  199.     w = strlen(msg);
  200.     if (w < sizeof(anykey)-2)
  201.         w = sizeof(anykey)-2;
  202.     w += 4;
  203.     h = 5;
  204.     bordery = (termheight()-h)/2;
  205.     borderx = (termwidth()-w)/2;
  206.  
  207.     empty_box(bordery, borderx, w+2, h+2);
  208.  
  209.     move(bordery+2, borderx+3);
  210.     printf("%s", msg);
  211.     move(bordery+4, borderx+3);
  212.     printf(anykey);
  213.     (void) getkey();
  214. }
  215.  
  216.  
  217.  
  218. /*
  219.  * Ask a yes/no question of the user.  Return 0 for no, 1 for yes.
  220.  */
  221. static int yesno(const char *msg) {
  222.     int key, h, w, borderx, bordery;
  223.     const char keys[] = "Y=Ret=Yes  N=No";
  224.  
  225.     w = strlen(msg);
  226.     if (w < sizeof(keys)-2)
  227.         w = sizeof(keys)-2;
  228.     w += 4;
  229.     h = 5;
  230.     bordery = (termheight()-h)/2;
  231.     borderx = (termwidth()-w)/2;
  232.  
  233.     empty_box(bordery, borderx, w+2, h+2);
  234.  
  235.     move(bordery+2, borderx+3);
  236.     printf("%s", msg);
  237.     move(bordery+4, borderx+3);
  238.     printf(keys);
  239.     do {
  240.         key = getkey();
  241.     } while (key != 'y' && key != 'Y' && key != 'n' && key != 'N' &&
  242.         key != '\r' && key != '\n');
  243.     return key != 'n' && key != 'N';
  244. }
  245.  
  246.  
  247.  
  248. /*
  249.  * Query the user.  "ques" is the question; "ans" will get the answer
  250.  * (or at most "n" chars of it, including terminating zero).  Return
  251.  * 0 for default answer, 1 for new answer, -1 for error (probably aborted
  252.  * response by user).  Let user edit previous answer.
  253.  */
  254. static int query_user(const char *ques, char *ans, size_t n) {
  255.     int h, i, w, first, key, len, start, borderx, bordery;
  256.  
  257.     w = termwidth()-6;
  258.     h = 7;
  259.     bordery = (termheight()-h)/2;
  260.     borderx = (termwidth()-w)/2;
  261.  
  262.     empty_box(bordery, borderx, w+2, h+2);
  263.  
  264.     move(bordery+2, borderx+3);
  265.     printf("%s", ques);
  266.     move(bordery+5, borderx+3);
  267.     for (i = 0; i < w-4; ++i)
  268.         printf("-");
  269.     move(bordery+7, borderx+3);
  270.     printf("Ret=Accept  Esc=Cancel");
  271.  
  272.     start = 0;
  273.     i = len = strlen(ans);
  274.     first = 1;
  275.     for (;;) {
  276.         if (i < start)
  277.             start = i;
  278.         else if (i-start >= w-4)
  279.             start = i-(w-4)+1;
  280.  
  281.         move(bordery+4, borderx+3);
  282.         if (first)
  283.             standout();
  284.         printf("%-*.*s", w-4, w-4, ans+start);
  285.         standend();
  286.         move(bordery+4, borderx+3+i-start);
  287.         key = getkey();
  288.  
  289.         switch (key) {
  290.         case '\n':
  291.         case '\r':
  292.             return len > 0;
  293.  
  294.         case '\033':    /* esc */
  295.             return -1;
  296.  
  297.         case '\002':    /* ctrl-b */
  298.             if (i > 0)
  299.                 --i;
  300.             break;
  301.  
  302.         case '\006':    /* ctrl-f */
  303.             if (i < len)
  304.                 ++i;
  305.             break;
  306.  
  307.         case '\001':    /* ctrl-a */
  308.             i = 0;
  309.             break;
  310.  
  311.         case '\005':    /* ctrl-e */
  312.             i = len;
  313.             break;
  314.  
  315.         case '\b':    /* ctrl-h */
  316.         case '\177':    /* DEL */
  317.             if (first) {
  318.                 i = len = 0;
  319.                 ans[0] = '\0';
  320.             } else if (i > 0) {
  321.                 --i;
  322.                 strdel(ans+i, 1);
  323.             }
  324.             break;
  325.  
  326.         case '\004':    /* ctrl-d */
  327.             if (first) {
  328.                 i = len = 0;
  329.                 ans[0] = '\0';
  330.             } else if (i < len)
  331.                 strdel(ans+i, 1);
  332.             break;
  333.  
  334.         default:
  335.             if (first) {
  336.                 i = len = 0;
  337.                 ans[0] = '\0';
  338.             }
  339.             if (i < n-1 && isprint(key)) {
  340.                 strcins(ans+i, key);
  341.                 ++len;
  342.                 ++i;
  343.             }
  344.             break;
  345.         }
  346.  
  347.         first = 0;
  348.     }
  349. }
  350.  
  351.  
  352.  
  353. /*
  354.  * Signal and exit handlers
  355.  */
  356. static jmp_buf exitjmp;
  357. static sig_atomic_t stilljump = 0;
  358.  
  359. static void terminate(int dummy) {
  360.     stilljump = 0;
  361.     longjmp(exitjmp, 1);
  362. }
  363. static void do_exit(void) {
  364.     if (stilljump)
  365.         longjmp(exitjmp, 1);
  366. }
  367.  
  368.  
  369.  
  370. /*
  371.  * The database and the current location in it
  372.  */
  373. static struct lsm_database db;
  374. static int top;
  375. static int current;
  376. static int modified = 0;
  377. static int more;
  378.  
  379.  
  380. /*
  381.  * Display hlep line at the top and status line at the bottom.
  382.  */
  383. static void help_and_status(void) {
  384.     char buf[1024];
  385.     int w;
  386.  
  387.     w = termwidth() - 2;
  388.     move(0, 0);
  389.     clrtoeol();
  390.     standout();
  391.     printf("%-*s", w, "  N=Next  P=Prev  Q=Quit  /=Search forward  ?=Backward");
  392.     standend();
  393.  
  394.     sprintf(buf, "  Entry %lu/%lu (%s)", (unsigned long) current+1,
  395.         (unsigned long) db.nentries, more ? "more" : "bottom");
  396.     if (modified)
  397.         strcat(buf, "  ** MODIFIED **");
  398.     move(termheight()-1, 0);
  399.     clrtoeol();
  400.     standout();
  401.     printf("%-*s", w, buf);
  402.     standend();
  403. }
  404.  
  405.  
  406.  
  407. /*
  408.  * Display a list of strings on the screen.  Let user scroll through it.
  409.  * Return the key the user pressed that wasn't a scrolling key.  *cur
  410.  * is the location of the cursor (cur == NULL means no cursor), *top means
  411.  * the topmost visible string (top == NULL means start at top); both are
  412.  * updated when the user scrolls.
  413.  */
  414. static int display_strings(char **list, int n, int *cur, int *top) {
  415.     int i, w, col, dirty, dummy_top, key, max_visible, top_row;
  416.  
  417.     if (cur == NULL)
  418.         col = 0;
  419.     else
  420.         col = 4;
  421.  
  422.     if (top == NULL) {
  423.         dummy_top = 0;
  424.         cur = top = &dummy_top;
  425.     }
  426.  
  427.     w = termwidth()-2 - col;
  428.     top_row = 2;
  429.     max_visible = termheight()-4;
  430.  
  431.     dirty = 1;
  432.     for (;;) {
  433.         if (*cur < *top) {
  434.             *top = *cur;
  435.             dirty = 1;
  436.         } else if (*cur >= max_visible && *top < *cur-max_visible+1) {
  437.             *top = *cur - max_visible + 1;
  438.             dirty = 1;
  439.         }
  440.  
  441.         if (dirty) {
  442.             for (i = 0; i < max_visible; ++i) {
  443.                 move(top_row + i, 0);
  444.                 clrtoeol();
  445.                 move(top_row + i, col);
  446.                 if (*top + i < n)
  447.                     printf("%-*s", w, list[*top + i]);
  448.             }
  449.             dirty = 0;
  450.         }
  451.  
  452.         if (col > 0) {
  453.             move(top_row + *cur - *top, 0);
  454.             printf("-->");
  455.         }
  456.  
  457.         help_and_status();
  458.         key = getkey();
  459.  
  460.         if (col > 0) {
  461.             move(top_row + *cur - *top, 0);
  462.             printf("   ");
  463.         }
  464.  
  465.         switch (key) {
  466.         case KEY_UP:
  467.         case 'p':
  468.         case 'P':
  469.             if (col == 0)
  470.                 return KEY_UP;
  471.             if (*cur > 0)
  472.                 --(*cur);
  473.             break;
  474.  
  475.         case KEY_DOWN:
  476.         case 'n':
  477.         case 'N':
  478.             if (col == 0)
  479.                 return KEY_DOWN;
  480.             if (*cur+1 < n)
  481.                 ++(*cur);
  482.             break;
  483.  
  484.         case KEY_NEXT:
  485.         case ' ':
  486.             if (*top+1 < n) {
  487.                 *top += max_visible-2;
  488.                 if (*top >= n)
  489.                     *top = n-1;
  490.                 *cur = *top;
  491.                 dirty = 1;
  492.             }
  493.             break;
  494.  
  495.         case KEY_PREV:
  496.         case '-':
  497.             if (*top > 0) {
  498.                 *top -= max_visible-2;
  499.                 if (*top < 0)
  500.                     *top = 0;
  501.                 *cur = *top;
  502.                 dirty = 1;
  503.             }
  504.             break;
  505.         default:
  506.             return key;
  507.         }
  508.     }
  509. }
  510.  
  511.  
  512. /*
  513.  * Build list of strings from the titles; one per string.  Return a
  514.  * pointer to a static list that will overwritten or modified or
  515.  * reallocated or otherwise mucked with by the next call to this function.
  516.  * Return NULL if something failed.
  517.  */
  518. static char **build_title_list(struct lsm_database *db, int *count) {
  519.     char *list[10240];
  520.     int i, title;
  521.  
  522.     title = lsm_field_to_index("Title");
  523.     for (i = 0; i < db->nentries; ++i) {
  524.         char *p = db->entries[i].fields[title];
  525.         list[i] = strdup(p == NULL ? "" : p);
  526.         if (list[i] == NULL) {
  527.             while (--i >= 0)
  528.                 free(list[i]);
  529.             return NULL;
  530.         }
  531.         strtrim(list[i]);
  532.     }
  533.     *count = db->nentries;
  534.     return list;
  535. }
  536.  
  537.  
  538. /*
  539.  * Build a list of strings from one LSM entry.  Return a
  540.  * pointer to a static list that will overwritten or modified or
  541.  * reallocated or otherwise mucked with by the next call to this function.
  542.  * Return NULL if something failed.
  543.  */
  544. static char **build_entry_strings(struct lsm_entry *e, int *count) {
  545.     static char *list[10240];
  546.     static int n;
  547.     char *p, *q, *s, buf[10240];
  548.     int i, w, len, startcol;
  549.  
  550.     while (--n >= 0)
  551.         free(list[n]);
  552.     n = 0;
  553.  
  554.     startcol = 16;
  555.     w = termwidth() - 2;
  556.  
  557.     for (i = 0; i < MAX_FIELDS; ++i) {
  558.         if (e->fields[i] == NULL)
  559.             continue;
  560.  
  561.         sprintf(buf, "%s:", lsm_index_to_field(i));
  562.         s = e->fields[i];
  563.         do {
  564.             for (len = strlen(buf); len < startcol; ++len)
  565.                 buf[len] = ' ';
  566.             buf[len] = '\0';
  567.  
  568.             while (*s != '\n' && isspace(*s))
  569.                 ++s;
  570.             p = s + strcspn(s, "\n");
  571.             sprintf(buf+len, "%.*s", (int)(p-s), s);
  572.  
  573.             len = strlen(buf);
  574.             p = buf;
  575.             while (len > 0) {
  576.                 if (len > w) {
  577.                     q = p + w;
  578.                     while (q > p && !isspace(*q))
  579.                         --q;
  580.                     if (q == p)
  581.                         q = p + w;
  582.                 } else
  583.                     q = p + len;
  584.                 list[n] = strndup(p, q-p);
  585.                 if (list[n] == NULL) {
  586.                     while (--n >= 0) free(list[n]);
  587.                     n = 0;
  588.                     return NULL;
  589.                 }
  590.                 ++n;
  591.                 len -= q-p;
  592.                 p = q;
  593.             }
  594.             buf[0] = '\0';
  595.  
  596.             s = (*p == '\n') ? p+1 : p;
  597.         } while (*s != '\0');
  598.     }
  599.  
  600.     *count = n;
  601.     return list;
  602. }
  603.  
  604.  
  605.  
  606. int main(int argc, char **argv) {
  607.     int i, n, build_titles, ntitles, done, key, pg, list_mode;
  608.     char pat[10240];
  609.     char fname[10240];
  610.     char dbname[10240];
  611.     FILE *f;
  612.     char **list, **titles;
  613.  
  614.     __set_liberror(__exit_on_error | __complain_on_error);
  615.     set_progname(argv[0], "lsmtool");
  616.  
  617.     if (argc != 2)
  618.         errormsg(1, 0, "usage: %s lsmdatabase\n", get_progname());
  619.  
  620.     if (strlen(argv[1]) >= sizeof(dbname))
  621.         errormsg(1, 0, "filename too long, max = %lu",
  622.             (unsigned long) sizeof(dbname));
  623.     strcpy(dbname, argv[1]);
  624.     f = fopen(dbname, "r");
  625.     if (f != NULL) {
  626.         if (lsm_read_database(f, &db) == -1)
  627.             errormsg(1, 0, "error in database `%s'", dbname);
  628.         xfclose(f);
  629.     }
  630.     modified = 0;
  631.  
  632.     terminit();
  633.  
  634.     if (setjmp(exitjmp) != 0) {
  635.         clear();
  636.         termend();
  637.         return EXIT_FAILURE;
  638.     }
  639.     signal(SIGINT, terminate);
  640.  
  641.     signal(SIGTSTP, SIG_IGN);
  642.  
  643.     stilljump = 1;
  644.     atexit(do_exit);
  645.  
  646.     top = 0;
  647.     current = 0;
  648.     done = 0;
  649.     pg = 1;
  650.     list_mode = 1;
  651.     build_titles = 1;
  652.  
  653.     clear();
  654.     while (!done) {
  655.         help_and_status();
  656.         if (list_mode) {
  657.             if (build_titles) {
  658.                 titles = build_title_list(&db, &ntitles);
  659.                 if (titles == NULL) {
  660.                     clear();
  661.                     termend();
  662.                     errormsg(1, -1, "failed building "
  663.                         "titles, out of memory?");
  664.                     exit(EXIT_FAILURE);
  665.                 }
  666.                 build_titles = 0;
  667.             }
  668.             key = display_strings(titles, ntitles, ¤t, &top);
  669.         } else {
  670.             list = build_entry_strings(&db.entries[current], &n);
  671.             if (list == NULL) {
  672.                 clear();
  673.                 termend();
  674.                 errormsg(1, 0,
  675.                     "failed building list, out of memory?");
  676.                 exit(EXIT_FAILURE);
  677.             }
  678.             key = display_strings(list, n, NULL, NULL);
  679.         }
  680.  
  681.         switch (key) {
  682.         case '\f':
  683.             clear();
  684.             break;
  685.  
  686.         case '\r': case '\n':
  687.         case 'i': case 'I':
  688.         case '+':
  689.             list_mode = !list_mode;
  690.             break;
  691.  
  692.         case 'h': case 'H':
  693.             help();
  694.             clear();
  695.             break;
  696.  
  697.         case 'q': case 'Q':
  698.             done = !modified ||
  699.               yesno("Database has been modified, quit anyway?");
  700.             break;
  701.  
  702.         case KEY_DOWN:
  703.         case 'n': case 'N':
  704.             if (current+1 < db.nentries)
  705.                 ++current;
  706.             break;
  707.  
  708.         case KEY_UP:
  709.         case 'p': case 'P':
  710.             if (current > 0)
  711.                 --current;
  712.             break;
  713.  
  714.         case 'g':
  715.             current = 0;
  716.             pg = 1;
  717.             break;
  718.  
  719.         case 'G':
  720.             if (db.nentries == 0)
  721.                 current = 0;
  722.             else
  723.                 current = db.nentries - 1;
  724.             pg = 1;
  725.             break;
  726.  
  727.         case '\\':
  728.         case '?':
  729.             if (query_user("Search backward for what?", pat, sizeof(pat)) == -1)
  730.                 break;
  731.             i = search(&db, current, -1, pat);
  732.             if (i == current)
  733.                 notify("Not found");
  734.             else
  735.                 current = i;
  736.             break;
  737.  
  738.         case '/':
  739.             if (query_user("Search forward for what?", pat, sizeof(pat)) == -1)
  740.                 break;
  741.             i = search(&db, current, 1, pat);
  742.             if (i == current)
  743.                 notify("Not found");
  744.             else
  745.                 current = i;
  746.             break;
  747.  
  748.         case 'w':
  749.         case 'W':
  750.             if (current == db.nentries) {
  751.                 notify("No current entry");
  752.                 break;
  753.             }
  754.             if (query_user("Append entry to which file?", fname, sizeof(fname)) == -1)
  755.                 break;
  756.             if ((f = fopen(fname, "a")) == NULL) {
  757.                 notify("Couldn't open the file");
  758.                 break;
  759.             }
  760.             if (lsm_write_one_entry(f, &db.entries[current]) == -1)
  761.                 notify("The write failed");
  762.             (void) fclose(f);
  763.             break;
  764.  
  765.         case 's':
  766.         case 'S':
  767.             if (query_user("Save to which file?", dbname, sizeof(dbname)) == -1)
  768.                 break;
  769.             if ((f = fopen(dbname, "w")) == NULL) {
  770.                 notify("Couldn't open the file");
  771.                 break;
  772.             }
  773.             if (lsm_write_database(f, &db) == -1)
  774.                 notify("The save failed");
  775.             else
  776.                 modified = 0;
  777.             (void) fclose(f);
  778.             break;
  779.  
  780.         case 'd':
  781.         case 'D':
  782.             if (current < db.nentries) {
  783.                 memdel(&db.entries[current], 
  784.                     (db.nentries-current)*sizeof(*db.entries),
  785.                     sizeof(*db.entries));
  786.                 --db.nentries;
  787.                 if (current > 0 && current == db.nentries)
  788.                     --current;
  789.                 build_titles = 1;
  790.             }
  791.             modified = 1;
  792.             break;
  793.  
  794.         case '$':
  795.             sort_database(&db);
  796.             build_titles = 1;
  797.             modified = 1;
  798.             break;
  799.  
  800.         case 'u':
  801.         case 'U':
  802.             current = next_duplicate(&db, current);
  803.             break;
  804.         }
  805.     }
  806.  
  807.     stilljump = 0;
  808.     clear();
  809.     termend();
  810.  
  811.     return 0;
  812. }
  813.